抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

How to play?

GitHub仓库 将整个文件克隆到本地。

在终端依次执行如下指令:

1
npm install
1
npx harhat play

一个方便测试的 在线网站

操作码大全

1. puzzle_01

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
############
# Puzzle 1 #
############

00 34 CALLVALUE
01 56 JUMP
02 FD REVERT
03 FD REVERT
04 FD REVERT
05 FD REVERT
06 FD REVERT
07 FD REVERT
08 5B JUMPDEST
09 00 STOP

分析:

CALLVALUE表示传入的msg.value,其单位是wei

JUMP则是表示调转到JUMPDEST标志的位置,比如在本题中CALLVALUE = 5,则会执行0x05位置的代码,不出所料应该是会报错的。

image-20230819142508563

所以本题只要发送8,则可以成功执行。

结果:

image-20230819143801991

2. puzzle_02

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
############
# Puzzle 2 #
############

00 34 CALLVALUE
01 38 CODESIZE
02 03 SUB
03 56 JUMP
04 FD REVERT
05 FD REVERT
06 5B JUMPDEST
07 00 STOP
08 FD REVERT
09 FD REVERT

分析:

CALLVALUE和上一题一样,CODESIZE则表示代码的大小,而从题目中不难看出09表示代码大小为10bytes,而CODESIZE的单位也是bytes,所以CODESIZE的值是10

这里和逆波兰表达有点区别,这里的减法表示的是:当读取到SUB操作码时,EVM读取栈的顺序为:CODESIZE SUB CALLVALUE10 - CALLVALUE,要使本题成功执行,则需跳转到0610 - CALLVALUE = 06,所以不难得出CALLVALUE = 4。

结果:

image-20230819143744916

3. puzzle_03

源码:

1
2
3
4
5
6
7
8
9
10
############
# Puzzle 3 #
############

00 36 CALLDATASIZE
01 56 JUMP
02 FD REVERT
03 FD REVERT
04 5B JUMPDEST
05 00 STOP

分析:

还是为了跳转到被JUMPDEST标志的04栈的位置,而CALLDATASIZE则是统计calldata数据的长度,所以只需随便发送一个4bytes的数据即可,因为只能发送十六进制,且在十六进制中两位数字表示1bytes,所以简单设置calldata = 0x00000000即可。

结果:

image-20230819144638954

4. puzzle_04

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
############
# Puzzle 4 #
############

00 34 CALLVALUE
01 38 CODESIZE
02 18 XOR
03 56 JUMP
04 FD REVERT
05 FD REVERT
06 FD REVERT
07 FD REVERT
08 FD REVERT
09 FD REVERT
0A 5B JUMPDEST
0B 00 STOP

分析:

重点是XOR,其表示按位异或。不难看出CODESIZE= 12,通关条件为CALLVALUE ^ CODESIZE = a = 10 = 1010,通过简单的计算不难算出CALLVALUE的值。

1
2
3
CODESIZE    1100
JUMPDEST 1010
CALLVALUE 0110

所以发送6,即可通关。

结果:

image-20230819145502183

5. puzzle_05

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
############
# Puzzle 5 #
############

00 34 CALLVALUE
01 80 DUP1
02 02 MUL
03 610100 PUSH2 0100
06 14 EQ
07 600C PUSH1 0C
09 57 JUMPI
0A FD REVERT
0B FD REVERT
0C 5B JUMPDEST
0D 00 STOP
0E FD REVERT
0F FD REVERT

分析:

DUP1:复制堆栈中的第一个值并将其推入堆栈的第一个位置(DUP2:表示复制第二个值,并将其推入堆栈的第一个位置,其他同理);

MUL:表示将前两个值进行相乘并压入栈;

PUSH2 0100:表示将0100这两个字节的值压入栈顶,PUSH1, PUSH3,PUSH4...表示压入几个字节,最大压入32bytes,即PUSH32;

EQ:弹出前两个值,比较其值是否相同,如果相同则压入1,否则压入0

PUSH1 0C:表示将一个字节的0c压入栈顶;

JUMPI:当JUMPI执行时,它会从堆栈中弹出 2 个值。第一个值将是要跳转到的新程序计数器(一如既往,它必须是有效JUMPDEST指令)。第二个值是一个布尔标志(0 或 1),用于评估是否必须跳转。如果值为1则跳转;否则继续执行下一条指令。

综上,要是执行到JUMPI指令之前,栈中元素为 00:0c 01:01

只要执行 CALLVALUE DUP1 MUL PUSH2 0100 EQ之后,返回的值是true,即可通过(CALLVALUE ^ CALLVALUE == 0x0100 = 16 ^ 2 = 256),所以发送16便可以通关。

结果:

image-20230819153911414

6. puzzle_06

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
############
# Puzzle 6 #
############

00 6000 PUSH1 00
02 35 CALLDATALOAD
03 56 JUMP
04 FD REVERT
05 FD REVERT
06 FD REVERT
07 FD REVERT
08 FD REVERT
09 FD REVERT
0A 5B JUMPDEST
0B 00 STOP

分析:

PUSH1 00:往栈中压入 0bytes

CALLDATALOAD:表示从指定位置开始读取32bytes的数据,题中则是表示偏移量为0,即从索引为0的坐标开始读取32bytes的数据,读取到的数据的值为0a,即可通关。

结果:

image-20230819163130164

7. puzzle_07

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
############
# Puzzle 7 #
############

00 36 CALLDATASIZE
01 6000 PUSH1 00
03 80 DUP1
04 37 CALLDATACOPY
05 36 CALLDATASIZE
06 6000 PUSH1 00
08 6000 PUSH1 00
0A F0 CREATE
0B 3B EXTCODESIZE
0C 6001 PUSH1 01
0E 14 EQ
0F 6013 PUSH1 13
11 57 JUMPI
12 FD REVERT
13 5B JUMPDEST
14 00 STOP

分析:

p1:

1
2
3
4
00      36        CALLDATASIZE
01 6000 PUSH1 00
03 80 DUP1
04 37 CALLDATACOPY

三个堆栈输入分别是

  1. destOffset:将复制结果的内存中的字节偏移量。
  2. offset:要复制的调用数据中的字节偏移量。
  3. size:要复制的字节大小。

CALLDATACOPY就像一个“特殊”MLOAD,直接从 calldata 位置获取要存储在内存中的数据。这些指令的意思是:从 calldata 中取出所有数据并将其复制到从内存位置 0 开始的内存中。

p2:

1
2
3
4
05      36        CALLDATASIZE
06 6000 PUSH1 00
08 6000 PUSH1 00
0A F0 CREATE

它的堆栈输入分别是:

  1. value:以Wei为单位的值发送到新帐户。
  2. offset内存中的字节偏移量(以字节为单位),新帐户的初始化代码。
  3. size:要复制的字节大小(初始化代码的大小)。

堆栈输出:

  1. address:已部署合约的地址,如果部署失败,则为 0。

p3:

1
2
3
4
5
0B      3B        EXTCODESIZE
0C 6001 PUSH1 01
0E 14 EQ
0F 6013 PUSH1 13
11 57 JUMPI

EXTCODESIZE获取已部署合约的大小(以字节为单位)并将其添加到堆栈中。之后,谜题检查已部署合约的大小是否等于值 1。如果是,我们按照 到达该JUMPI位置13并赢得挑战。

The solution is to find a calldata value for which the result of EXTCODESIZE (done on the contract deployed with code from the calldata itself) return 1.

具体分析博客可见如下大佬博客:

link1

link2

重点:

When the CREATE opcode is executed, only the code returned by the RETURN opcode will be the “runtime code” that will be executed in the future when the deployed contract will be called. The other part of the bytecode is just used once, only for the constructor part.

*个人见解:通过create操作码创建的合约地址,[...] 创建代码在事务中执行,该事务返回运行时代码的副本,这是合约的实际代码。正如我们将看到的,构造函数是创建代码的一部分,而不是运行时代码的一部分。合约的构造函数是创建代码的一部分;一旦部署,它就不会出现在合约的代码中。,实际上初始化合约的代码指令为执行calldata之后,**通过执行calldata中的RETURN语句返回的代码才是将来调用部署的合约时执行的runtime code*

分析RETURN:

  • 从开始读取位置的内存偏移量
  • 要读取和返回的内存大小(以字节为单位)

简单来说,其返回的值是从memory中读取的,从哪读取,读取多少取决于offset,size

所以,只要按要求拼接自定义calldata即可,原则是:将一条指令写入memory,且通过RETURNmemory中返回这一条指令用于初始化合约,这样一来,合约中便只有一条代码,EXTCODESIZE返回的值便是1

1
2
3
4
5
6
7
8
9
10
## 拼接calldata ##
## 将calldata写入memory
PUSH1 60 ff ## mstore'value,只要是一个字节即可
PUSH1 60 00 ## mstore'offset,在memory内存中的存储起始索引
## 这里采用MSTORE8,这在memory中的存储方式为:0xff00000000000...(32位)
MSTORE8 53 ## MSTORE8 操作码,在memory中写入1bytes
## 从memory中返回代码,用来执行合约的初始化
PUSH1 60 01 ## RETURN'size,从memory中读取代码的大小(长度)
PUSH1 60 00 ## RETURN'offset,从memory中读取代码的起始索引
RETURN f3 ## RETURN 操作码,从memory中返回runtime code

所以,构造出来的calldata便是:

1
0x60ff60005360016000f3

结果: (如下这个结果同理也可以,0x60ff6000526001601ff3)

image-20230819172816082

8. puzzle_08

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
############
# Puzzle 8 #
############

00 36 CALLDATASIZE
01 6000 PUSH1 00
03 80 DUP1
04 37 CALLDATACOPY
05 36 CALLDATASIZE
06 6000 PUSH1 00
08 6000 PUSH1 00
0A F0 CREATE
0B 6000 PUSH1 00
0D 80 DUP1
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 94 SWAP5
12 5A GAS
13 F1 CALL
14 6000 PUSH1 00
16 14 EQ
17 601B PUSH1 1B
19 57 JUMPI
1A FD REVERT
1B 5B JUMPDEST
1C 00 STOP

分析:

p1:

1
2
3
4
00      36        CALLDATASIZE
01 6000 PUSH1 00
03 80 DUP1
04 37 CALLDATACOPY

calldata拷贝到memory中。

p2:

1
2
3
4
05      36        CALLDATASIZE
06 6000 PUSH1 00
08 6000 PUSH1 00
0A F0 CREATE

根据拷贝在memory中的calldata创建合约地址。

p3:

1
2
3
4
5
6
7
8
0B      6000      PUSH1 00
0D 80 DUP1
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 94 SWAP5
12 5A GAS
13 F1 CALL

SWAP5:将栈顶的00CRETAE创建的合约地址交换位置。

分析CALL:

  • gas: the amount of gas to send to the sub context created for the execution.
  • address: the address on which the context will be executed
  • value: value in wei to send to the address
  • argsOffset: byte offset in the memory in number of bytes
  • argsSize: byte size to copy from the memory with the previously specified offset
  • retOffset: byte offset in memory in bytes from which you want to store the return data returned by the execution
  • retSize: byte size to copy from the returned data

p4:

1
2
3
4
14      6000      PUSH1 00
16 14 EQ
17 601B PUSH1 1B
19 57 JUMPI

即要求合约调用失败,EQ的返回值才为1,程序才可以正确执行。

所以只要本着调用失败去实现即可,又FD REVERT,所以可以在puzzles_7的基础上进行修改即可。

用于初始化的指令为FD,则有

1
calldata = 0x60fd60005360016000f3

结果:

image-20230820140304529

9. puzzle_09

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
############
# Puzzle 9 #
############

00 36 CALLDATASIZE
01 6003 PUSH1 03
03 10 LT
04 6009 PUSH1 09
06 57 JUMPI
07 FD REVERT
08 FD REVERT
09 5B JUMPDEST
0A 34 CALLVALUE
0B 36 CALLDATASIZE
0C 02 MUL
0D 6008 PUSH1 08
0F 14 EQ
10 6014 PUSH1 14
12 57 JUMPI
13 FD REVERT
14 5B JUMPDEST
15 00 STOP

分析:

p1:

1
2
3
4
5
00      36        CALLDATASIZE
01 6003 PUSH1 03
03 10 LT
04 6009 PUSH1 09
06 57 JUMPI

要求calldata的长度小于3bytesLT表示小于)

p2:

1
2
3
4
5
6
7
0A      34        CALLVALUE
0B 36 CALLDATASIZE
0C 02 MUL
0D 6008 PUSH1 08
0F 14 EQ
10 6014 PUSH1 14
12 57 JUMPI

通过p1之后,要求`msg.value ^ calldatasize == 0x08。

简单,令calldata = 0x00000006,msg.sender = 2

结果:

image-20230820141422133

10. puzzle_10

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#############
# Puzzle 10 #
#############

00 38 CODESIZE
01 34 CALLVALUE
02 90 SWAP1
03 11 GT
04 6008 PUSH1 08
06 57 JUMPI
07 FD REVERT
08 5B JUMPDEST
09 36 CALLDATASIZE
0A 610003 PUSH2 0003
0D 90 SWAP1
0E 06 MOD
0F 15 ISZERO
10 34 CALLVALUE
11 600A PUSH1 0A
13 01 ADD
14 57 JUMPI
15 FD REVERT
16 FD REVERT
17 FD REVERT
18 FD REVERT
19 5B JUMPDEST
1A 00 STOP

分析:

p1:

1
2
3
4
5
6
7
8
00      38          CODESIZE
01 34 CALLVALUE
02 90 SWAP1
03 11 GT
04 6008 PUSH1 08
06 57 JUMPI
07 FD REVERT
08 5B JUMPDEST

CALLVALUECODESIZE交换位置,CODESIZE = 0x1A = 26,要求msg.value < 26。

p2:

1
2
3
4
5
6
7
8
9
09      36          CALLDATASIZE
0A 610003 PUSH2 0003
0D 90 SWAP1
0E 06 MOD
0F 15 ISZERO
10 34 CALLVALUE
11 600A PUSH1 0A
13 01 ADD
14 57 JUMPI

ISZERO 要求: CALLDATASIEZE % 3 == 0,且msg.value + 0x0a == 0x19 == 25,所以msg.value=15

故:

1
2
CALLDATASIEZE % 3 == 0;
msg.value == 15;

所以: calldata 可为 0x000006

结果:

image-20230820142713104

完结

image-20230820142748827

参考链接

link1link2

评论



政策 · 统计 | 本站使用 Volantis 主题设计